﻿namespace WebPacker.Repositories
{
	using System;
	using System.Threading;
	using WebPacker.Configuration;
	using WebPacker.Helpers;
	using WebPacker.Logger;
	using WebPacker.Packs;
	using WebPacker.Packs.Builders;
	using WebPacker.Packs.Resources;
	using WebPacker.Packs.Watchers;

	public class PackRepository : IPackRepository, IDisposable
	{
		private static readonly ILogger logger = LogFactory.GetLogger();

		private readonly ReaderWriterLockSlim packLock;
		private readonly object buildSyncRoot;
		private readonly IPackBuilder packBuilder;
		private IResourceWatcher sourceWatcher;
		private PackCollection packs;
		private bool disposed;
		private string source;

		public PackRepository(IPackBuilder packBuilder)
		{
			this.packs = new PackCollection();
			this.packLock = new ReaderWriterLockSlim();
			this.buildSyncRoot = new object();
			this.packBuilder = packBuilder;
		}

		public string Source
		{
			get 
			{ 
				return source; 
			}

			set 
			{
				if (value != source)
				{
					source = value;
					UpdateSource();
				}
			}
		}

		public IPack GetPack(PackType packType, string packName)
		{
			try
			{
				if (packName == null)
				{
					throw new ArgumentNullException("packName");
				}

				logger.Debug(() => string.Format("Get pack {0}.", LogHelper.ToString(packType, packName)));

				var pack = TryGetPack(packType, packName);

				if (pack == null)
				{
					throw new WebPackerException(string.Format("Pack {0} isn't found.", LogHelper.ToString(packType, packName)));
				}

				return pack;
			}
			catch (Exception e)
			{
				logger.Exception(string.Format("Error at get pack {0}.", LogHelper.ToString(packType, packName)), e);
				throw;
			}
		}

		public IPack GetPack(PackType packType, string[] resources)
		{
			string packName = null;

			try
			{
				if (resources == null)
				{
					throw new ArgumentNullException("resources");
				}

				packName = packBuilder.GeneratePackName(packType, resources);

				logger.Debug(() => string.Format("Get pack {0}.", LogHelper.ToString(packType, packName)));

				var pack = TryGetPack(packType, packName);

				if (pack == null)
				{
					lock (buildSyncRoot)
					{
						pack = TryGetPack(packType, packName);

						if (pack != null)
						{
							return pack;
						}

						pack = packBuilder.BuildPack(packType, resources);

						try
						{
							packLock.EnterWriteLock();

							if (!packs.Contains(packType, packName))
							{
								packs.Add(pack);
							}
						}
						finally
						{
							packLock.ExitWriteLock();
						}
					}
				}

				return pack;
			}
			catch (Exception e)
			{
				logger.Exception(string.Format("Error at get pack {0}.", LogHelper.ToString(packType, packName)), e);
				throw;
			}
		}

		public virtual void SetPacks(PackInfoCollection packInfos)
		{
			logger.Info(() => "Initialization packs..");

			try
			{
				var packs = new PackCollection();

				if (packInfos != null)
				{
					if (packInfos.Monitoring != null)
					{
						packBuilder.DefaultMonitoring = (bool)packInfos.Monitoring;
					}

					if (packInfos.VersionGeneratorName != null)
					{
						packBuilder.DefaultVersionGenerator = packInfos.VersionGeneratorName;
					}

					foreach (var packInfo in packInfos)
					{
						try
						{
							packs.Add(packBuilder.BuildPack(packInfo));
						}
						catch (Exception e)
						{
							logger.Exception(string.Format("Error at initializing pack {0}.", packInfo.ToLogString()), e);
						}
					}
				}

				var lastPacks = this.packs;

				lock (buildSyncRoot)
				{
					try
					{
						packLock.EnterWriteLock();
						this.packs = packs;
					}
					finally
					{
						packLock.ExitWriteLock();
					}
				}

				foreach (var pack in lastPacks)
				{
					pack.Dispose();
				}
			}
			catch (Exception e)
			{
				logger.Exception("Error at packs initialization.", e);
				throw;
			}
		}

		public void Dispose()
		{
			Dispose(true);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing && !disposed)
			{
				if (sourceWatcher != null)
				{
					sourceWatcher.Dispose();
				}

				foreach (var pack in packs)
				{
					pack.Dispose();
				}

				packLock.Dispose();
				disposed = true;
			}
		}

		private IPack TryGetPack(PackType packType, string packName)
		{
			IPack pack;

			try
			{
				packLock.EnterReadLock();
				packs.TryGetItem(packType, packName, out pack);
			}
			finally
			{
				packLock.ExitReadLock();
			}

			return pack;
		}

		private void UpdateSource()
		{
			try
			{
				if (sourceWatcher != null)
				{
					sourceWatcher.Dispose();
					sourceWatcher = null;
				}

				if (source != null)
				{
					ReloadPacks();
					logger.Info(() => string.Format("Begin watch for changes of packs (\"{0}\").", source));
					sourceWatcher = new FileWatcher();
					sourceWatcher.Add(new FileResource(VirtualPathHelper.ToPhisicalPath(source)));
					sourceWatcher.Changed += (sender, ea) => ReloadPacks();
				}
				else
				{
					logger.Info(() => "Stop watch for changes of packs.");
				}
			}
			catch (Exception e)
			{
				logger.Exception(string.Format("Error at update source of packs (\"{0}\").", source), e);
				throw;
			}
		}

		private void ReloadPacks()
		{
			try
			{
				if (source != null)
				{
					var path = VirtualPathHelper.ToPhisicalPath(source);
					var packInfos = WebPackerConfiguration.DeserializePackInfoCollection(path);
					SetPacks(packInfos);
				}
			}
			catch (Exception e)
			{
				logger.Exception(string.Format("Error at reload packs from \"{0}\".", source), e);
				throw;
			}
		}
	}
}